﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using PowerAwareBluetooth.Controller.IO;

namespace PowerAwareBluetooth.Controller.AI
{
    /// <summary>
    /// learns how a user is using his device by time
    /// </summary>
    public class Learner
    {
        public enum TimeSliceLength
        {
            ONE_MINUTE = 1,
            FIVE_MINUTE = 5,
            TEN_MINUTE = 10,
            FIFTEEN_MINUTE = 15,
            THIRTY_MINUTE = 30
        }
        
        //slices repeat after a week
        private static TimeSliceLength DEFAULT_SLICE_LENGTH = TimeSliceLength.TEN_MINUTE;
        private static int DEFAULT_MINIMUM_ON_INTERVAL = 30; // in minutes
        
        private int TIME_SLICE_LENGTH;
        private int TOTAL_TIME_SLICES_NUM;
        private int SLICES_PER_DAY;
        private int SLICES_PER_HOUR;
        private int SLICES_FOR_MINIMUM_ON_INTERVAL; //represents the minimum time inetrval the bluetooth is on

        private StateMachine[] m_timeLine; 

        public Learner(TimeSliceLength timeSliceLength)
        {
            int daysInWeek  = 7;
            int hoursInDay = 24;
            int minutesInHour = 60;

            TIME_SLICE_LENGTH = (int)timeSliceLength;
            SLICES_PER_HOUR = minutesInHour / TIME_SLICE_LENGTH;
            SLICES_PER_DAY = SLICES_PER_HOUR * hoursInDay;
            TOTAL_TIME_SLICES_NUM = SLICES_PER_DAY * daysInWeek;
            SLICES_FOR_MINIMUM_ON_INTERVAL = DEFAULT_MINIMUM_ON_INTERVAL / (TIME_SLICE_LENGTH * 2);

            LoadStateMachine();
            
            for (int i = 0; i < TOTAL_TIME_SLICES_NUM; i++)
            {
                m_timeLine[i] = new StateMachine();
            }
            

        }

        private void LoadStateMachine()
        {
            // loads the state machine array from the storage card
            m_timeLine = IOManager.Load(IOManager.SaveType.LEARNED_TIMELINE) as StateMachine[];
            if (m_timeLine == null)
            {
                m_timeLine = new StateMachine[TOTAL_TIME_SLICES_NUM];
            }


        }

        public Learner() : this(DEFAULT_SLICE_LENGTH) { }

        /// <summary>
        /// Learn the given result, and (if needed) save the results to the Storage Card
        /// </summary>
        /// <param name="result">true means bluetooth in use false means bluetooth is off</param>
        public void Learn(bool result, int interval)
        {
            if (interval < 0)
            {
                interval = 0;
            }

            if (result)
            {
                m_timeLine[interval].IncrementState();
            }
            else
            {
                m_timeLine[interval].DecrementState();
            }

            // save to hard disk
            IOManager.Save(m_timeLine, IOManager.SaveType.LEARNED_TIMELINE);
        }

        /// <summary>
        /// whether to activate the bluetooth or not.
        /// this function refers to Now
        /// </summary>
        /// <returns></returns>
        public bool IsLearntBehaviourOn()
        {
            return IsCurrentStateInOnInterval();
        }

        /// <summary>
        /// gets and sets the minimum interval the  bluetooth is on 
        /// works in minutes
        /// </summary>
        public int MinimumOnInterval
        {
            get
            {
                return SLICES_FOR_MINIMUM_ON_INTERVAL * 2 * TIME_SLICE_LENGTH;
            }
            set
            {
                SLICES_FOR_MINIMUM_ON_INTERVAL = value / (TIME_SLICE_LENGTH * 2);
            }
        }

        /// <summary>
        /// reset the learnt behaviour
        /// </summary>
        public void ResetLearntBehaviour()
        {
            for (int i = 0; i < TOTAL_TIME_SLICES_NUM; i++)
            {
                m_timeLine[i].Reset();
            }

            
            IOManager.Save(m_timeLine, IOManager.SaveType.LEARNED_TIMELINE);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns>the next time in which a new learning interval will occur</returns>
        public DateTime NextLearningInterval()
        {
            DateTime time = DateTime.Now;
            int currSlice = CurrentInterval();
            while (currSlice == TimeToSliceNum(time))
            {
                time = time.AddMinutes(1);
            }

            return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, 0);
        }

        /// <summary>
        /// returns the time of the current interval beginning
        /// </summary>
        /// <returns></returns>
        public DateTime CurrentIntervalBeginning()
        {
            DateTime time = DateTime.Now;
            int currSlice = CurrentInterval();
            TimeSpan minute = new TimeSpan(0,1,0);
            while (currSlice == TimeToSliceNum(time))
            {
                time = time.Subtract(minute);
            }
            //at loop end we are 1 minute before current slice beginning
            time =  time.AddMinutes(1);

            return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, 0);
        }

        /// <summary>
        /// do the two times belong to the same learning interval
        /// </summary>
        /// <param name="dt1"></param>
        /// <param name="dt2"></param>
        /// <returns></returns>
        public bool SameInterval(DateTime dt1, DateTime dt2)
        {
            return (TimeToSliceNum(dt1) == TimeToSliceNum(dt2));
        }

        /// <summary>
        /// the learning interval length in minutes
        /// </summary>
        public long LearningIntervalLength
        {
            get
            {
                return TIME_SLICE_LENGTH;
            }
        }

        /// <summary>
        /// returns the current interval number
        /// </summary>
        /// <returns></returns>
        public int CurrentInterval()
        {
            return TimeToSliceNum(DateTime.Now);

        }

        #region Private Methods     

        private int TimeToSliceNum(DateTime time)
        {
            int day = (int)time.DayOfWeek; //sunday = 0, monday = 1, ...
            int hour = time.Hour;
            int minutes = time.Minute;

            return ((day) * SLICES_PER_DAY) + (hour * SLICES_PER_HOUR) + (minutes / TIME_SLICE_LENGTH);

        }

        private bool IsCurrentStateInOnInterval()
        {
            int currentSlice = CurrentInterval();
            bool res = false;
            
            //determine search start and end points
            int startSlice = currentSlice - SLICES_FOR_MINIMUM_ON_INTERVAL;
            int endSlice = currentSlice + SLICES_FOR_MINIMUM_ON_INTERVAL;

            //check that search is within array bound
            if (startSlice < 0)
            {
                startSlice = TOTAL_TIME_SLICES_NUM + startSlice;
            }
            if (endSlice > TOTAL_TIME_SLICES_NUM - 1)
            {
                endSlice = endSlice - TOTAL_TIME_SLICES_NUM;
            }
            
            //search for an ON slice
            for (int i = startSlice; i != endSlice; i++)
            {
                if (m_timeLine[i].CurrentState >= StateMachine.States.ON)
                {
                    res = true;
                    break;
                }
                if (i == TOTAL_TIME_SLICES_NUM - 1)
                {
                    //reached array limit, start from array beginning
                    i = -1;
                }
            }
          
            return res;
        }

        #endregion
    }


}
